package com.lhz.framework.aspect.log;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.lhz.common.constant.ResponseState;
import com.lhz.common.utils.UUIDUtils;
import com.lhz.common.utils.ip.IpUtils;
import com.lhz.framework.exception.ServiceException;
import com.lhz.framework.security.HolderInfoManager;
import com.lhz.project.system.model.entity.auth.OperateLog;
import com.lhz.project.system.service.base.LogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author LiHuaZhi
 * @since 2020-06-20 13:42:57
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {


    @Value("${spring.profiles.active}")
    private String profiles;

    @Value("${sys.request.log.record}")
    private Boolean recordSysLog;

    @Resource
    private LogService logService;

    @Resource
    private HttpServletRequest request;

    private final static String LOG_RECORD_CONTEXT = "LogRecordContext";

    /**
     * 以 controller 包下定义的所有请求为切入点
     */
    @Pointcut("@annotation(com.lhz.framework.aspect.log.annotation.SysLog)")
    public void logPoint() {
    }


    /**
     * 环绕通知，调用目标方法
     */
    @Around("logPoint()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        LogRecordContext logRecordContext = new LogRecordContext();

        // 记录日志上下文
        logRecordContext.setStartTime(System.currentTimeMillis());
        logRecordContext.setPath(request.getServletPath());
        logRecordContext.setMethod(request.getMethod());
        logRecordContext.setRequestParam(argsToJson(proceedingJoinPoint.getArgs()));

        // 请求的controller类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        // //请求的controller方法
        String requestType = proceedingJoinPoint.getSignature().getName();

        logRecordContext.setClassName(className);
        logRecordContext.setRequestType(requestType);

        request.setAttribute(LOG_RECORD_CONTEXT, logRecordContext);

        // 调用目标方法
        return proceedingJoinPoint.proceed();
    }

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(value = "logPoint()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPoint()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    private void handleLog(final JoinPoint joinPoint, final Exception e, Object result) {
        // 方法执行开始时间
        LogRecordContext logRecordContext = (LogRecordContext) request.getAttribute(LOG_RECORD_CONTEXT);
        request.removeAttribute(LOG_RECORD_CONTEXT);

        // 方法执行完成时间
        Long startTime = logRecordContext.getStartTime();
        long endTime = System.currentTimeMillis();
        long useTime = endTime - startTime;

        int responseCode = ResponseState.SUCCESS.getCode();
        String errorTrace = null;
        String responseStr = null;
        String message = null;
        int isSuccess = 1;
        if (e != null) {
            responseCode = ResponseState.FAIL.getCode();
            // 设置日志信息
            String trace = ExceptionUtils.getStackTrace(e);
            errorTrace = trace.length() > 2000 ? trace.substring(0, 2000) : trace;
            message = e.getMessage();
            responseStr = message;
            isSuccess = 2;
            if (e instanceof ServiceException) {
                responseCode = ((ServiceException) e).getCode();
                responseStr = JSONUtil.toJsonStr(e);
            }
            log.error("=>> LOG_ERROR  Path: {}; Method: {}; RequestParam: {}; ResponseParam : {}; Time: {} ms;", request.getServletPath(), request.getMethod(),
                    logRecordContext.getRequestParam(), responseStr, useTime);
            log.error("Exception: ", e);
        } else {
            if (result != null) {
                responseStr = JSONUtil.toJsonStr(result);
            }

            log.info("=>> LOG_INFO  Path: {}; Method: {}; RequestParam: {}; ResponseParam : {}; Time: {} ms;", request.getServletPath(), request.getMethod(),
                    logRecordContext.getRequestParam(), responseStr, useTime);
        }

        // 记录顺序：类名、方法名、是否成功、状态码、耗时(ms)
        log.info("=>> LOG_DIGEST  {}|{}|{}|{}|{}", logRecordContext.className, logRecordContext.requestType, isSuccess == 1, responseCode, useTime);

        // 如果需要记录日志到数据库
        if (recordSysLog) {
            responseStr = StrUtil.isNotBlank(responseStr) && responseStr.length() > 2000 ? responseStr.substring(0, 2000) : responseStr;
            OperateLog sysLog = setSysLog(logRecordContext);
            // 设置日志信息
            sysLog.setResponseParams(responseStr);
            sysLog.setErrorMsg(message);
            sysLog.setErrorTrace(errorTrace);
            sysLog.setIsSuccess(isSuccess);
            sysLog.setTime(useTime);
            // 插入
            logService.insertOperateLog(sysLog);
        }
    }

    /**
     * 设置日志信息
     */
    private OperateLog setSysLog(LogRecordContext logRecordContext) {
        OperateLog sysLog = new OperateLog();
        sysLog.setId(UUIDUtils.getUuId());
        sysLog.setIp(IpUtils.getIpAddr(request));
        sysLog.setUrl(request.getServletPath());
        sysLog.setUserId(HolderInfoManager.getUserId());
        sysLog.setUserName(HolderInfoManager.getUserName());
        sysLog.setHttpMethod(request.getMethod());
        sysLog.setRequestParams(logRecordContext.getRequestParam());
        sysLog.setOperateType(logRecordContext.getClassName());
        sysLog.setOperateEvent(logRecordContext.getRequestType());

        return sysLog;
    }

    private String argsToJson(Object[] args1) {
        String s = null;
        try {
            s = JSONUtil.toJsonStr(args1);
        } catch (Exception e) {
            log.error("=========== 日志请求参数获取失败 ===========");
            // System.out.println(e);
        }
        return s;
    }

    @Data
    private static class LogRecordContext {
        /**
         * 请求开始时间
         */
        private Long startTime;

        /**
         * 请求路径
         */
        private String path;

        /**
         * 请求方法
         */
        private String method;

        /**
         * 请求参数
         */
        private String requestParam;

        /**
         * 请求的controller类名
         */
        private String className;

        /**
         * 请求的controller方法
         */
        private String requestType;
    }
}